﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace IntegratedOnBordDebug
{
    public class UDPAsync
    {
        #region "Class Variables"
        /// <summary>
        /// local listening port
        /// </summary>
        public int PortToListenTo { get; set; } = 0;

        /// <summary>
        /// remote port
        /// </summary>
        public int PortToSendTo { get; set; } = 0;

        /// <summary>
        /// local listening ipv4
        /// </summary>
        public string LocalListenIP { get; set; } = string.Empty;
        public bool IsListening;
        // call backs for send/recieve!
        public UdpState udpState;
        #endregion

        #region "Class Events"
        public delegate void DataRcvdEventHandler(byte[] DData, IPEndPoint RIpEndPoint);
        public event DataRcvdEventHandler OnDataReceived;
        public delegate void ErrEventHandler(string Msg);
        public event ErrEventHandler OnError;
        #endregion

        //class constructors
        public UDPAsync()
        {
            IsListening = false;
        }
        //overrides pass the port to listen to/sendto and startup
        public UDPAsync(int PortToListenTo, int PortToSendTo, string ListenIP)
        {
            try
            {
                IsListening = false;
                this.PortToListenTo = PortToListenTo;
                this.PortToSendTo = PortToSendTo;
                LocalListenIP = ListenIP;
                StartListener();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void ChangeListen(string IPTarget, int PortTarget)
        {
            IsListening = false;
            if (udpState.udpClient != null)
            {
                udpState.udpClient.Close();
                this.PortToListenTo = PortTarget;
            }
            else
            {

            }

        }

        //string property to contain the class name
        private string ClassName
        {
            get
            {
                return "UDPAsync";
            }
        }

        //function to send data as a byte stream to a remote socket
        // modified to work as a callback rather than a block
        public void SendData(byte[] Data)
        {
            try
            {
                udpState.udpClient.BeginSend(Data, Data.Length, new IPEndPoint(IPAddress.Broadcast, PortToSendTo), new AsyncCallback(OnDataSent), udpState);
                udpState.udpClient.BeginSend(Data, Data.Length, "255.255.255.255", 5000, new AsyncCallback(OnDataSent), udpState);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Common.Log(string.Format("UDP发送数据异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
            }
        }

        /// <summary>p2p send data
        /// 
        /// </summary>
        /// <param name="IpAddress"></param>
        /// <param name="Data"></param>
        public void SendData(string IpAddress, byte[] Data)
        {
            try
            {
                if (string.IsNullOrEmpty(IpAddress))
                {
                    udpState.udpClient.BeginSend(Data, Data.Length, new IPEndPoint(IPAddress.Broadcast, PortToSendTo), new AsyncCallback(OnDataSent), udpState);
                    udpState.udpClient.BeginSend(Data, Data.Length, "255.255.255.255", 5000, new AsyncCallback(OnDataSent), udpState);
                }
                else
                {
                    udpState.udpClient.BeginSend(Data, Data.Length, IpAddress, PortToSendTo, new AsyncCallback(OnDataSent), udpState);
                    udpState.udpClient.BeginSend(Data, Data.Length, IpAddress, 5000, new AsyncCallback(OnDataSent), udpState);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Common.Log(string.Format("UDP发送数据异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
            }
        }

        // This is the call back function, which will be invoked when a client is connected
        public void OnDataSent(IAsyncResult asyn)
        {

            try
            {
                //get the data
                UdpClient ii = (UdpClient)asyn;
                ii.EndSend(asyn); // stop the send call back
            }
            catch (Exception ex)
            {
                if (IsListening == true)
                {
                    OnError?.Invoke(ex.Message);
                    Console.WriteLine(ex.Message);
                }
            }
        }

        //function to start the listener call back everytime something is recieved
        private void IniListnerCallBack()
        {
            try
            {
                // start teh recieve call back method
                udpState.udpClient.BeginReceive(new AsyncCallback(OnDataRecieved), udpState);
            }
            catch (Exception ex)
            {
                if (IsListening == true)
                {
                    OnError?.Invoke(ex.Message);
                    Console.WriteLine(ex.Message);
                }
            }
        }
        private static volatile object lockReceive = new object();
        // This is the call back function, which will be invoked when a client is connected
        public void OnDataRecieved(IAsyncResult asyn)
        {
            lock (lockReceive)
            {

                byte[] receiveBytes;
                UdpClient u;
                IPEndPoint e;

                try
                {

                    u = ((UdpState)(asyn.AsyncState)).udpClient;
                    e = ((UdpState)(asyn.AsyncState)).ipEndPoint;

                    receiveBytes = u.EndReceive(asyn, ref e);
                    Common.Log(string.Format("回应：{0}", Common.BytesToHex(receiveBytes)));
                    //raise the event with the data recieved
                    OnDataReceived?.Invoke(receiveBytes, e);

                    //记录发送数据，调试用
                }
                catch (Exception ex)
                {
                    OnError?.Invoke(ex.Message);
                    Console.WriteLine(ex.Message);
                    Common.Log(string.Format("UDP接收数据异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
                }
                finally
                {
                    u = null;
                    e = null;
                    receiveBytes = null;
                    // recall the call back
                    IniListnerCallBack();
                }

            }
        }

        //function to start the listener 
        //if the the listner is active, destroy it and restart
        // shall mark the flag that the listner is active
        private void StartListener()
        {
            // byte[] receiveBytes; // array of bytes where we shall store the data recieved
            IPAddress ipAddress;
            IPEndPoint ipLocalEndPoint;

            try
            {
                IsListening = false;
                //resolve the net card ip address
                ipAddress = string.IsNullOrEmpty(LocalListenIP) ? IPAddress.Any : IPAddress.Parse(LocalListenIP);
                LocalListenIP = ipAddress.ToString();

                //get the ipEndPoint
                ipLocalEndPoint = new IPEndPoint(ipAddress, PortToListenTo);
                // if the udpclient interface is active destroy
                if (udpState.udpClient != null)
                    udpState.udpClient.Close();
                udpState.udpClient = null;
                udpState.ipEndPoint = null;
                //re initialise the udp client

                udpState = new UdpState();
                udpState.ipEndPoint = ipLocalEndPoint;
                udpState.udpClient = new UdpClient(ipLocalEndPoint);


                if (PortToListenTo == 0)
                    PortToListenTo = ((IPEndPoint)udpState.udpClient.Client.LocalEndPoint).Port;

                IsListening = true; // set to start listening
                                    // wait for data
                IniListnerCallBack();
            }
            catch (Exception ex)
            {
                if (IsListening == true)
                {
                    Console.WriteLine(ex.Message);
                    Common.Log(string.Format("UDP开启监听异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
                }
            }
            finally
            {
                if (udpState.udpClient == null)
                {
                    Thread.Sleep(1000);
                    StartListener();
                }
                else
                {
                    ipAddress = null;
                    ipLocalEndPoint = null;
                }
            }
        }

        //stop the listener thread
        public void StopListener()
        {
            try
            {
                IsListening = false;
                if (udpState.udpClient != null)
                    udpState.udpClient.Close();
                udpState.udpClient = null;
                udpState.ipEndPoint = null;

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Common.Log(string.Format("UDP关闭监听异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
            }
        }

        public InfoRef SendData(string IPv4Addr, object pInfo, bool isBrodcast = false)
        {
            InfoRef result = new InfoRef();
            if (pInfo != null)
            {
                try
                {
                    #region 调试
                    //byte[] sendData = BuildBytes(pInfo);
                    //Console.WriteLine("SendByUDP-->" + BitConverter.ToString(sendData));
                    //local.BeginSend(sendData, sendData.Length, ipep, new AsyncCallback(SendCallback), local);
                    //local.BeginReceive(new AsyncCallback(ReceiveCallback), local);
                    //result.State = InfoEnum.emState.Succeed;
                    #endregion

                    byte[] cmdData = { };
                    if (pInfo is InfoPacket)
                    {
                        InfoPacket packet = pInfo as InfoPacket;
                        cmdData = InfoPacket.ModelToByte(packet);

                        //记录发送数据，调试用
                        Common.Log(string.Format("发送：{0}", Common.BytesToHex(cmdData)));
                    }

                    if (isBrodcast)
                    {
                        SendData(cmdData);  //广播发送
                    }
                    else
                    {
                        SendData(IPv4Addr, cmdData);    //单播发送
                    }

                    result.State = InfoEnumClass.emState.Succeed;

                    //记录发送数据
                    //Log.Write(string.Format("发送数据至 {0}--{1} 数据：{2}", ip, portRemote, Common.BytesToHex(sendData)));

                }
                catch (Exception ex)
                {
                    Common.Log(string.Format("UDP发送数据异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
                    result.State = InfoEnumClass.emState.Failied;
                }
            }
            else
            {
                result.State = InfoEnumClass.emState.Failied;
            }
            return result;
        }

        /// <summary>字节数组转换成十六进制字符显示
        /// 
        /// </summary>
        /// <param name="ArrayBytes">目标字节数组</param>
        /// <returns></returns>
        private string HexToString(byte[] ArrayBytes, string Split = " ")
        {
            string hexStr = "";
            for (int i = 0; i < ArrayBytes.Length; i++)
            {
                hexStr += hexStr.Equals("") ? ArrayBytes[i].ToString("X2") : Split + ArrayBytes[i].ToString("X2");
            }
            return hexStr;
        }

        #region Business

        #endregion

        //dispose of all resources
        ~UDPAsync()
        {
            try
            {
                StopListener();
                if (udpState.udpClient != null) udpState.udpClient.Close();
                udpState.udpClient = null; udpState.ipEndPoint = null;
                LocalListenIP = null;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Common.Log(string.Format("UDP对象析构异常：{0}\r\n{1}", ex.Message, ex.StackTrace));
            }
        }

        //class that shall hold the reference of the call backs
        public struct UdpState
        {
            public IPEndPoint ipEndPoint; //define an end point
            public UdpClient udpClient; //define a client
        }
    }

}
